package com.kkh.tp;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.Pipe;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;




public class SCTransport extends Thread  {
	private static final String tag="tptr";
	
	public static final int MSG_CONNECTING=1;
	public static final int MSG_CONNECTED=2;
	public static final int MSG_DISCONNECTED=3;
	public static final int MSG_READ=4;
	public static final int MSG_ACCEPTED=5;
	
	public static final int STATUS_DISCONNECTED=0;
	public static final int STATUS_CONNECTING=2;
	public static final int STATUS_CONNECTED=3;
	public static final int STATUS_ACCEPTED=4;
	
	private Handler mHandler;
	private int mMaxChannels;
	private LinkedList<SCChannel> mChannels;
	private LinkedList<TPCmd> mCmds;
	
	private Selector selector;
	private boolean exit_thread=false;
	private int controlCmd, cmdPara1, cmdPara2;
	private ByteBuffer comBuffer;// = ByteBuffer.allocateDirect(128);
	private Context mContext;
	private DatagramChannel mSearchChannel=null;
	
	private Pipe mPipe;
	private Pipe.SourceChannel mRecvCmdCh;
	private Pipe.SinkChannel mSendCmdCh;
	
	private byte[] mPipeBuf;
	
	SockThreadCmd cmdObj = new SockThreadCmd();


	
	
	public SCTransport (Context context) {
		mContext = context;
		init();
		try {
			mPipe = Pipe.open();
			mPipeBuf = new byte[128];
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			selector = Selector.open();
			mRecvCmdCh = mPipe.source();
			mSendCmdCh = mPipe.sink();
			mRecvCmdCh.configureBlocking(false);
			mSendCmdCh.configureBlocking(false);
			mRecvCmdCh.register(selector, SelectionKey.OP_READ);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		Log.d(tag, "Com thread started...");
		
		try{
			process_loop();
		}catch(IOException e) {
			Log.e(tag, e.getMessage());
		}
		
		Log.d(tag, "Com thread ended....");
	}
	
	private void process_loop() throws IOException {
		//RemoteSys rs;
		
		boolean exit_main=false;
		LocalChannelData localdata;
		
		while(!exit_main) {
			try {
				int chs = selector.select();
				
				Set<SelectionKey> selectedKeys = selector.selectedKeys();
				Iterator<SelectionKey> itr = selectedKeys.iterator();
				//Log.d(tag, "chs="+chs+" keysize="+selectedKeys.size());
				
				while(itr.hasNext()) {
					SelectionKey key = itr.next();
					//rs = (RemoteSys)key.attachment();
					SelectableChannel sch = key.channel();

					
					//Log.d(tag, "key ch="+sch.toString());
					//Log.d(tag, "    key ="+key.toString());
					
					int ops = key.readyOps();
					//Log.d(tag, " read ops = " + ops);
					if(key.isValid() && key.isReadable()) {
						//Log.d(tag, "read ready....");
						ByteBuffer buf = ByteBuffer.allocate(128);
						
						
						if(sch == mRecvCmdCh) {
							int cmdret = patchCmd();
							if(cmdret != 0) {
								exit_main = true;
								break;
							}
								
						}
						else {
							localdata = (LocalChannelData)key.attachment();
							byte[] rb = new byte[1500];
							ByteBuffer byteBuffer = ByteBuffer.wrap(rb);
							//int rn = ((SocketChannel)sch).read(byteBuffer);
							int rn = localdata.schannel.read(rb);
							if(rn>0) {
								Log.d(tag, "read count="+rn);
								//ch.sendEvent(SCChannel.MSG_READ, rb, rn);
								sendCtxEvent(MSG_READ, rn, 0, localdata.schannel, rb);
							}
							else if(rn == -1)
							{
								sch.close();
								//ch.sendEvent(ch.MSG_DISCONNECTED);
								sendCtxEvent(MSG_DISCONNECTED, localdata.schannel);
							}
							
							
						}
					}
					
					if(key.isValid() && key.isConnectable()) {
						Log.d(tag, "connectable ......");
						localdata = (LocalChannelData)key.attachment();
						SocketChannel sck = (SocketChannel)sch;
						if(sck.isConnectionPending())
						{
                            boolean cnn;
                            try {
                                cnn =	((SocketChannel)sch).finishConnect();
                            } catch(IOException e) {
                                cnn = false;
                            }

							if(cnn) {
								key.interestOps(SelectionKey.OP_READ);
								boolean r = localdata.schannel.OnStatusChanged(STATUS_CONNECTED);
								if(!r)
									sendCtxEvent(MSG_CONNECTED, localdata.schannel);
							}
                            else {
                                Log.d(tag, "### connection fail...");
                                sck.close();
                                boolean r = localdata.schannel.OnStatusChanged(STATUS_DISCONNECTED);
                                if(!r)
                                	sendCtxEvent(MSG_DISCONNECTED, localdata.schannel);
                            }
						}
						
//						else if(sck.isConnected()==true) {
//							Log.d(tag, "disconnected......");
//							SCChannel ch = (SCChannel)key.attachment();
//							ch.sendEvent(SCChannel.MSG_DISCONNECTED);
//							sch.close();
//							ch.socketChannel = null;
//						}
					}

					if(key.isValid() && key.isAcceptable()) {
						localdata = (LocalChannelData)key.attachment();
						ServerSocketChannel svrch = (ServerSocketChannel)sch;
						SocketChannel child = svrch.accept();
						child.configureBlocking(false);

                        AcceptInfo acceptInfo = new AcceptInfo();
                        acceptInfo.schannel = localdata.schannel;
                        acceptInfo.childSocketChannel = child;
                        acceptInfo.remoteAddr = (InetSocketAddress)child.socket().getRemoteSocketAddress();
                        
						//child.register(selector, SelectionKey.OP_READ, new LocalChannelData());
                        SCChannel child_schannel = localdata.schannel.OnAccept(child);
                        if(child_schannel == null)
                        	sendCtxEvent(MSG_ACCEPTED, acceptInfo);
                        else {
                        	LocalChannelData local = new LocalChannelData();
                        	local.schannel = child_schannel;
                        	child.register(selector, SelectionKey.OP_READ, local);
                        }
                        
					}
					itr.remove(); // very, very important

				}
			} catch(ClosedSelectorException sele) {
				//break;
				
			} /*catch(Exception e) {
				//e.printStackTrace();
				Log.d(tag, "### excepton = "+e.getMessage());
				//break;
			}*/
		}
		
	
		close_allsock();
	}

	
	public void postCmd(TPCmd cmd) {
		synchronized(mCmds) {
			cmd.isSync = false;
			mCmds.add(cmd);
		}
		byte[] array = new byte[1];
		array[0] = 100;
		ByteBuffer buffer = ByteBuffer.wrap(array); 
		try {
			int wcnt = mSendCmdCh.write(buffer);
			//Log.d(tag, "pipe w = " + wcnt);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public int sendCmd(TPCmd cmd) {
		synchronized(mCmds) {
			cmd.isSync = true;
			mCmds.add(cmd);
			byte[] array = new byte[1];
			array[0] = 100;
			ByteBuffer buffer = ByteBuffer.wrap(array); 
			try {
				int wcnt = mSendCmdCh.write(buffer);
				//Log.d(tag, "pipe w = " + wcnt);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			try {
				mCmds.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return -1;
			}
		}
		return 0;
	}
	
	
	public void sendCtxEvent(int msgid, int arg1, int arg2, Object obj) {
		Message msg;
		msg = new Message();
		msg.what = msgid;
		msg.arg1 = arg1;
		msg.arg2 = arg2;
		msg.obj = obj;
		mHandler.sendMessage(msg);
	}
	
	public void sendCtxEvent(int msgid, Object obj) {
		sendCtxEvent(msgid, 0, 0, obj);
	}
	
	public void sendCtxEvent(int msgid, int arg1, int arg2, Object obj1, Object obj2) {
		Object[] objarr = new Object[2];
		objarr[0] = obj1;
		objarr[1] = obj2;
		sendCtxEvent(msgid, arg1, arg2, objarr);
	}
	

	
	private int patchCmd()  {
		// TODO Auto-generated method stub
		
		ByteBuffer buffer = ByteBuffer.wrap(mPipeBuf);
		int rcnt;
		int ret=0;
		boolean exit_loop=false;
		try {
			rcnt = mRecvCmdCh.read(buffer);
			buffer.flip();
			//Log.d(tag, "read pipe , cnt="+rcnt);
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			return 0;
		}
		
		TPCmd cmd;
		for(int i=0;i<rcnt && exit_loop == false;i++)
		{
			
			synchronized(mCmds) {
				cmd = mCmds.poll();
				if(cmd == null)
					break;
				if(cmd.cmd == TPCmd.CMD_CONNECT) {
					
					/*
					SCChannel schannel = (SCChannel)cmd.obj;
					InetSocketAddress remoteaddr = (InetSocketAddress)cmd.objex;
					
					
					SocketChannel sch = null;
					try {
						sch = SocketChannel.open();
						sch.configureBlocking(false);
						//sch.socket().setTcpNoDelay(true);
	                    LocalChannelData data = new LocalChannelData();
	                    data.schannel = schannel;
	                    schannel.socketChannel = sch;
						sch.connect(remoteaddr);
						sch.register(selector, SelectionKey.OP_CONNECT, data);
					} catch (IOException e) {
						// TODO Auto-generated catch block
	                    Log.d(tag, "### connection fail....");
	                    if(sch != null) {
	                        try {
	                            sch.close();
	                        } catch (IOException e1) {
	                            e1.printStackTrace();
	                        }
	                    }
	                    sendCtxEvent(MSG_DISCONNECTED, schannel);
						e.printStackTrace();
					}
					Log.d(tag, "remote ip=" + remoteaddr.getAddress().getHostAddress() +", port="+ remoteaddr.getPort());
					*/
					SCChannel schannel = (SCChannel)cmd.obj;
					if(schannel.mType == 0) {
						procTcpConnect(cmd);
					}
					else {
						procUdpConnect(cmd);
					}
				}
				else if(cmd.cmd == TPCmd.CMD_CLOSE) {
					Log.d(tag, " pactch close cmd...");
					SCChannel schannel = (SCChannel)cmd.obj;
					 
					if(schannel.socketChannel != null) {
						try {
							SelectionKey key = schannel.socketChannel.keyFor(selector);
							schannel.socketChannel.close();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}

						schannel.socketChannel = null;
					}
				}
				else if(cmd.cmd == TPCmd.CMD_WRITE) {
					
					SCChannel schannel = (SCChannel)cmd.obj;
					Log.d(tag, "cmd write, type=" + schannel.mType);
					
					byte[] pkt = (byte[])cmd.objex;
					/*
					if(schannel.mType == 0) {
						SocketChannel sch = (SocketChannel)schannel.socketChannel;
						try {
							int wcnt = sch.write(ByteBuffer.wrap(pkt));
		                    if(wcnt < pkt.length) {
		                        Log.d(tag, "### write fail...");
		                        sch.close();
		                        sendCtxEvent(MSG_DISCONNECTED, schannel);
		                    }
						} catch (IOException e) {
							// TODO Auto-generated catch block
		                    Log.d(tag, "### write io error...");
		                    try {
		                        sch.close();
		                    } catch (IOException e1) {
		                        e1.printStackTrace();
		                    }
		                    sendCtxEvent(MSG_DISCONNECTED, schannel);
		                    e.printStackTrace();
						}
					}
					else {
						Log.d(tag, "udp write:.....");
						DatagramChannel sch = (DatagramChannel)schannel.socketChannel;
						try {
							int wcnt = sch.write(ByteBuffer.wrap(pkt));
							Log.d(tag, "    cnt="+wcnt);
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}*/
					schannel.write(pkt);
				}
				else if(cmd.cmd == TPCmd.CMD_LISTEN) {
					Log.d(tag, "cmd listen...");
					int port = cmd.arg1;
					SCChannel schannel = (SCChannel)cmd.obj;
					ServerSocketChannel svrch;
					try {
						svrch = ServerSocketChannel.open();
						svrch.configureBlocking(false);
						ServerSocket socket = svrch.socket();
						InetSocketAddress addr = new InetSocketAddress(port);
						socket.bind(addr);
						schannel.socketChannel = svrch;
						
						LocalChannelData data = new LocalChannelData();
						data.schannel = schannel;
						svrch.register(selector, SelectionKey.OP_ACCEPT, data);
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
				}
				else if(cmd.cmd == TPCmd.CMD_ACCEPT_COMPLETE) {
					Log.d(tag, "accept complete...");
					SCChannel schannel = (SCChannel)cmd.obj;
					LocalChannelData data = new LocalChannelData();
					data.schannel = schannel;
					try {
						schannel.socketChannel.register(selector, SelectionKey.OP_READ, data);
					} catch (ClosedChannelException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				else if(cmd.cmd == TPCmd.CMD_TERMINATE_PROCESS) {
					ret = -1;
					exit_loop = true;	
				}
				else if(cmd.cmd == TPCmd.CMD_BIND) {
					SCChannel schannel = (SCChannel)cmd.obj;
					int port = cmd.arg1;
					if(schannel.socketChannel == null) {
						try {
							DatagramChannel sch = DatagramChannel.open();
							sch.configureBlocking(false);
							DatagramSocket socket = sch.socket();
							
							InetSocketAddress addr = new InetSocketAddress(port);
							socket.bind(addr);
							schannel.socketChannel = sch; 
							LocalChannelData data = new LocalChannelData();
							data.schannel = schannel;
							sch.register(selector, SelectionKey.OP_READ, data);
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
				else {
					OnUserCommand(cmd);
				}
				
				if(cmd.isSync) 
					mCmds.notify();
			}
			
		}	
		return ret;
	}
	
	
	private int procTcpConnect(TPCmd cmd) {
		Log.d(tag, "proc tcp connect...");	
		SCChannel schannel = (SCChannel)cmd.obj;
		InetSocketAddress remoteaddr = (InetSocketAddress)cmd.objex;
		SocketChannel sch = null;
		try {
			sch = SocketChannel.open();
			sch.configureBlocking(false);
			sch.socket().setTcpNoDelay(true);
            LocalChannelData data = new LocalChannelData();
            data.schannel = schannel;
            schannel.socketChannel = sch;
			sch.connect(remoteaddr);
			sch.register(selector, SelectionKey.OP_CONNECT, data);
			Log.d(tag, "remote ip=" + remoteaddr.getAddress().getHostAddress() +", port="+ remoteaddr.getPort());
			return 0;
		} catch (IOException e) {
			// TODO Auto-generated catch block
            Log.d(tag, "### connection fail....");
            if(sch != null) {
                try {
                    sch.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            sendCtxEvent(MSG_DISCONNECTED, schannel);
			e.printStackTrace();
			return -1;
		}
	}
	
	private int procUdpConnect(TPCmd cmd) {
		Log.d(tag, "proc udp connect...");
		SCChannel schannel = (SCChannel)cmd.obj;
		InetSocketAddress remoteaddr = (InetSocketAddress)cmd.objex;
		DatagramChannel sch = (DatagramChannel)schannel.socketChannel;
		try {
			sch.connect(remoteaddr);
		} catch(IllegalStateException estat) {
			estat.printStackTrace();
			 
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return -1;
		}
		return 0;
	}
	
	protected void OnUserCommand(TPCmd cmd) {
		// TODO Auto-generated method stub
		
	}
	
	public void requestEnd() {
		TPCmd cmd = new TPCmd(TPCmd.CMD_TERMINATE_PROCESS);
		sendCmd(cmd);
		
		try {
			join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		
		//mSendCmdCh.close();
		//mRecvCmdCh.close();
		
	}
	
	public class SockThreadCmd {
		final static int CMD_CONNECT=1;
		final static int CMD_DISCONNECT=2;
		
		
		int cmd;
		int para1;
		int para2;
	}
	
	private void close_allsock() throws IOException {
		Set<SelectionKey> keyset = selector.keys();
		Iterator<SelectionKey> itr = keyset.iterator();
		for(;itr.hasNext();) {
			SelectionKey key = itr.next();
			key.channel().close();
			Log.d(tag, "close sock channel...");
		}
		
		if(mSearchChannel != null)
			mSearchChannel.close();
	}
	
	
//	public void sendCmd(int cmd, int p1, int p2) {
//		synchronized(cmdObj) {
//			cmdObj.cmd = cmd;
//			cmdObj.para1 = p1;
//			cmdObj.para2 = p2;
//			selector.wakeup();
//			try { 
//				cmdObj.wait(); 
//			} catch(Exception e) { 
//				Log.d(tag, e.getMessage());
//			};
//			cmdObj.cmd = 0;
//		};
//		
//	}
	
	
	
	private void processCmd(SockThreadCmd cmd) {
		//RemoteSys rs;
		switch(cmd.cmd) {
			
			case SockThreadCmd.CMD_CONNECT:
				//rs = getSys(cmd.para1);
				//if(rs != null) {
				//	doConnect(rs);
				//}
				break;
			case SockThreadCmd.CMD_DISCONNECT:
				break;
			
			default:
//				rs = getSys(cmd.para1);
//				if(rs != null) {
//					doSendRemoteCmd(rs, cmd.cmd );
//				}
				break;
				
		}
	}
	
	public void request(String cmd) {
		ByteBuffer buffer = ByteBuffer.wrap("cmd".getBytes());
		try {
			mSendCmdCh.write(buffer);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
	Context getContext() {
		return mContext;
	}
	
	
	void init() {
		mMaxChannels = 100;
		
		mCmds = new LinkedList<TPCmd>();
		
		mChannels = new LinkedList<SCChannel>();
		
		
		mHandler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				// TODO Auto-generated method stub
				super.handleMessage(msg);
				if(msg.what == MSG_CONNECTING) {
					//Object[] obja = (Object[])msg.obj;
					//SCChannel schannel = (SCChannel)obja[0];
					
				}
				else if(msg.what == MSG_CONNECTED) {
					SCChannel schannel = (SCChannel)msg.obj;
					schannel._procMsgConnect();
				}
				else if(msg.what == MSG_DISCONNECTED) {
					SCChannel schannel = (SCChannel)msg.obj;
					schannel._procMsgDisconnect();
				}
				else if(msg.what == MSG_READ) {
					Object[] objarr = (Object[])msg.obj;
					SCChannel schannel = (SCChannel)objarr[0];
					schannel._procMsgReceive((byte[])objarr[1], msg.arg1);
				}
				else if(msg.what == MSG_ACCEPTED) {
                    AcceptInfo acceptInfo = (AcceptInfo)msg.obj;
					SCChannel newch = new SCChannel(SCTransport.this);
					newch.remoteAddr = acceptInfo.remoteAddr;
					newch.socketChannel = acceptInfo.childSocketChannel;
					acceptInfo.schannel._procMsgAccept(acceptInfo.schannel, newch);
					
					TPCmd cmd = new TPCmd(TPCmd.CMD_ACCEPT_COMPLETE, 0, 0, newch);
					postCmd(cmd);
					
				}
			}};
		
	}
	
	
	void registerChannel(SCChannel schannel) {
		mChannels.add(schannel);
	}
	
	void deregiserChannel(SCChannel schannel) {
		mChannels.remove(schannel);
	}
	
	
	
}

class LocalChannelData {
	SCChannel schannel;
	Object obj;
}

class AcceptInfo {
    SCChannel schannel;
    SocketChannel childSocketChannel;
    InetSocketAddress remoteAddr;
}
